﻿#region using statements

using System;
using System.Collections.Generic;

using AeonOfStrife.Combat;
using AeonOfStrife.Misc;
using AeonOfStrife.Objects.Castle;

using AeonOfStrifeDatatypes.Units;

using Microsoft.Xna.Framework;

using NGen;

#endregion

namespace AeonOfStrife.Objects.Units
{
	public interface IUnitManager
	{
		void AddUnitType(string p_unitType, UnitStatsXMLReader p_unitStats);

		Unit CreateUnit(string p_unitType, TeamType p_team);

		void RemoveUnit(Unit p_unit);

		UnitStats GetStats(string p_unitType);

		IAttackable GetTarget(TeamType p_team);

		void UpdateUnits(TeamType p_team);

		int GetUnitCount(TeamType p_team);

		int GetEnemyCount(TeamType p_team, EnemyPriority p_priority);
	}

	public class UnitManager : IUnitManager
	{
		/// <summary>
		/// 	maps units name to their stats
		/// </summary>
		protected readonly Dictionary<string, UnitStats> m_dUnitStats = new Dictionary<string, UnitStats>();

		protected readonly Dictionary<EnemyPriority, List<Unit>> m_dLeftUnits = new Dictionary<EnemyPriority, List<Unit>>();

		protected readonly Dictionary<EnemyPriority, List<Unit>> m_dRightUnits = new Dictionary<EnemyPriority, List<Unit>>();

		#region constructor

		public UnitManager() {
			foreach(EnemyPriority value in Enum.GetValues(typeof(EnemyPriority))) {
				m_dLeftUnits.Add(value, new List<Unit>());
				m_dRightUnits.Add(value, new List<Unit>());
			}
		}

		#endregion

		#region IUnitManager Members

		/// <summary>
		/// 	adds a type of unit to the manager which can be created
		/// </summary>
		/// <param name = "p_unitType">the unit name</param>
		/// <param name = "p_unitStats">the unit stats parsed from the XML file</param>
		public void AddUnitType(string p_unitType, UnitStatsXMLReader p_unitStats) {
			if(m_dUnitStats.ContainsKey(p_unitType)) {
				throw new Exception("Parent type already exists:" + p_unitType);
			}

			var stats = new UnitStats(p_unitStats);
			m_dUnitStats.Add(p_unitType, stats);
		}

		/// <summary>
		/// 	creates specified unit for specified player
		/// </summary>
		/// <param name = "p_unitType">the unit to create</param>
		/// <param name = "p_team">unit goes under this team</param>
		/// <returns>the unit created</returns>
		public Unit CreateUnit(string p_unitType, TeamType p_team) {
			if(!m_dUnitStats.ContainsKey(p_unitType)) {
				throw new Exception("Unregistered unit type");
			}

			var unit = new Unit(m_dUnitStats[p_unitType]) {
				EnemyPriority = EnemyPriority.LOW,
				Team = p_team
			};

			switch(p_team) {
				case TeamType.LEFT:
					m_dLeftUnits[EnemyPriority.LOW].Add(unit);
					break;
				case TeamType.RIGHT:
					m_dRightUnits[EnemyPriority.LOW].Add(unit);
					break;
			}

			return unit;
		}

		public void RemoveUnit(Unit p_unit) {
			switch(p_unit.Team) {
				case TeamType.LEFT:
					m_dLeftUnits[p_unit.EnemyPriority].Remove(p_unit);
					break;
				case TeamType.RIGHT:
					m_dRightUnits[p_unit.EnemyPriority].Remove(p_unit);
					break;
			}
		}

		/// <summary>
		/// 	returns the stats for a specified unit
		/// </summary>
		/// <param name = "p_unitType">the unit for which to retrieve stats</param>
		/// <returns>the stats for the specified unit</returns>
		public UnitStats GetStats(string p_unitType) {
			if(!m_dUnitStats.ContainsKey(p_unitType)) {
				throw new Exception("Unregistered unit type");
			}

			return m_dUnitStats[p_unitType];
		}

		/// <summary>
		/// 	returns the highest priority target for the specified team
		/// </summary>
		/// <param name = "p_team">the team of the unit</param>
		/// <returns>the highest priority IAttackable object on the other team</returns>
		public IAttackable GetTarget(TeamType p_team) {
			var enums = Enum.GetValues(typeof(EnemyPriority));
			switch(p_team) {
				case TeamType.RIGHT:
					for(int i = enums.Length - 1; i >= 0; i--) {
						if(m_dLeftUnits[(EnemyPriority) enums.GetValue(i)].Count > 0) {
							return m_dLeftUnits[(EnemyPriority) enums.GetValue(i)][
								GameEngine.Random.Next(m_dLeftUnits[(EnemyPriority) enums.GetValue(i)].Count)
							];
						}
					}

					return GameEngine.GetService<GameData>().CastleLeft.GetRandomAttackPoint();
				case TeamType.LEFT:
					for(int i = enums.Length - 1; i >= 0; i--) {
						if(m_dRightUnits[(EnemyPriority)enums.GetValue(i)].Count > 0) {
							return m_dRightUnits[(EnemyPriority)enums.GetValue(i)][
								GameEngine.Random.Next(m_dRightUnits[(EnemyPriority)enums.GetValue(i)].Count)
							];
						}
					}
					return GameEngine.GetService<GameData>().CastleRight.GetRandomAttackPoint();
					
			}
			return null;
		}

		public void UpdateUnits(TeamType p_team) {
			var gameData = GameEngine.GetService<GameData>();
			var castleDistance = Math.Abs(gameData.CastleLeft.Position.X - gameData.CastleRight.Position.X);
			var increment = castleDistance / 5f;

			var enums = Enum.GetValues(typeof(EnemyPriority));

			for(int i = 0; i < enums.Length-1; i++) {
				foreach(var unit in m_dLeftUnits[(EnemyPriority)enums.GetValue(i)]) {
					if(Math.Abs(unit.Position.X - gameData.CastleLeft.Position.X) > increment * i+1) {
						m_dLeftUnits[(EnemyPriority)enums.GetValue(i+1)].Add(unit);
						unit.EnemyPriority = (EnemyPriority)enums.GetValue(i + 1);
					}
				}
				foreach(var unit in m_dLeftUnits[(EnemyPriority)enums.GetValue(i + 1)]) {
					m_dLeftUnits[(EnemyPriority) enums.GetValue(i)].Remove(unit);
				}

				foreach(var unit in m_dRightUnits[(EnemyPriority)enums.GetValue(i)]) {
					if(Math.Abs(unit.Position.X - gameData.CastleRight.Position.X) > increment * i + 1) {
						m_dRightUnits[(EnemyPriority)enums.GetValue(i + 1)].Add(unit);
						unit.EnemyPriority = (EnemyPriority)enums.GetValue(i + 1);
					}
				}
				foreach(var unit in m_dRightUnits[(EnemyPriority)enums.GetValue(i + 1)]) {
					m_dRightUnits[(EnemyPriority)enums.GetValue(i)].Remove(unit);
				}
			}
		}

		public int GetUnitCount(TeamType p_team) {
			var returnVal = 0;
			
			foreach(EnemyPriority value in Enum.GetValues(typeof(EnemyPriority))) {
				returnVal += p_team == TeamType.LEFT
						? m_dLeftUnits[value].Count : m_dRightUnits[value].Count;
			}
			
			return returnVal;
		}

		public int GetEnemyCount(TeamType p_team, EnemyPriority p_priority) {
			var returnVal = 0;

			foreach(EnemyPriority value in Enum.GetValues(typeof(EnemyPriority))) {
				if(p_priority.HasFlag(value)) {
					returnVal += p_team == TeamType.RIGHT 
						? m_dLeftUnits[value].Count : m_dRightUnits[value].Count;
				}
			}

			return returnVal;
		}
		#endregion
	}
}